home *** CD-ROM | disk | FTP | other *** search
/ Aminet 4 / Aminet 4 - November 1994.iso / aminet / dev / c / cweb31p9d.lha / CWeb / examples / sample.w < prev    next >
Text File  |  1994-03-28  |  10KB  |  269 lines

  1. % SAMPLE example of WEB/CWEB portability.  This documentation was
  2. % originally published in the ``Communications of the ACM'', Volume 29,5
  3. % (May 1986), and later in the book ``Literate Programming'', October 1991,
  4. % where it appeared as an example of WEB programming for Pascal.  It has
  5. % been translated into CWEB by Andreas Scherer to show the ease of
  6. % portability between Pascal/WEB and C/CWEB.  Minor changes to include
  7. % extra C functionality are mentioned in the text that follows.
  8.  
  9. % This program is distributed WITHOUT ANY WARRANTY, express or implied.
  10. % WEB Version --- Don Knuth, 1986
  11. % CWEB Version --- Andreas Scherer, 1993
  12.  
  13. % Copyright (c) 1993 Andreas Scherer
  14.  
  15. % Permission is granted to make and distribute verbatim copies of this
  16. % document provided that the copyright notice and this permission notice
  17. % are preserved on all copies.
  18.  
  19. % Permission is granted to copy and distribute modified versions of this
  20. % document under the conditions for verbatim copying, provided that the
  21. % entire resulting derived work is distributed under the terms of a
  22. % permission notice identical to this one.
  23.  
  24. \font\csc=cmcsc10
  25. \def\PASCAL/{{\csc Pascal\spacefactor1000}}
  26.  
  27. \def\title{SAMPLE (C Version 1.0)}
  28. \def\topofcontents{\null\vfill
  29.   \centerline{\titlefont Producing random numbers}
  30.   \vskip15pt
  31.   \centerline{(C Version 1.0)}
  32.   \vfill}
  33. \def\botofcontents{\vfill
  34.   \noindent Copyright \copyright\ 1993 Andreas Scherer
  35.   \bigskip
  36.   \noindent Permission is granted to make and distribute verbatim copies of
  37.   this document provided that the copyright notice and this permission
  38.   notice are preserved on all copies.
  39.   \smallskip
  40.   \noindent Permission is granted to copy and distribute modified versions
  41.   of this document under the conditions for verbatim copying, provided that
  42.   the entire resulting derived work is distributed under the terms of a
  43.   permission notice identical to this one.}
  44.  
  45. @* Introduction.  Jon Bentley@^Bentley, Jon Louis@> recently discussed
  46. the following interesting problem as one of his ``Programming Pearls''
  47. [{\sl Communications of the ACM}~{\bf 27} (December, 1984), 1179--1182]:
  48.  
  49. {\narrower\noindent The input consists of two integers $M$ and $N$, with
  50. $M<N${}.  The output is a sorted list of $M$ random numbers in the range
  51. $1\ldots N$ in which no integer occurs more than once.  For probability
  52. buffs, we desire a sorted selection without replacement in which each
  53. selection occurs equiprobably.\par}
  54.  
  55. The present program illustrates what I (i.e., Donald E. Knuth
  56. @^Knuth, Donald E.@> in his {\sl Literate Programming} (October, 1991),
  57. 144--149) think is the best solution to this problem, when $M$ is
  58. reasonably large yet small compared to $N${}.  It's the method described
  59. tersely in the answer to exercise 3.4.2--15 of my book {\sl Seminumerical
  60. Algorithms}, pp.~141 and~555.  The \.{WEB} program was translated to \.{CWEB}
  61. by Andreas Scherer.  Necessary changes for \CEE/ were included and some
  62. \PASCAL/ restrictions removed.
  63. @^Scherer, Andreas@>
  64.  
  65. @ For simplicity, all input and output in this program is assumed to be
  66. handled at the terminal.  The \.{CWEB} macro |read_terminal| defined here
  67. can easily be changed to accommodate other conventions.  The macro |trunc|
  68. replaces the \PASCAL/ routine of the same name for \CEE/.
  69. @^Differences between \PASCAL/ and \CEE/@>
  70.  
  71. @d read_terminal(a) fscanf(stdin,"%d",&a)
  72.    /* input a value from the terminal */
  73. @d trunc(a) (int)(a)
  74.  
  75. @ Here's an outline of the entire \CEE/ program:
  76.  
  77. @c
  78. @<Global \#|include|s@>@/
  79. @<Global variables@>@/
  80. @<The random number generation procedure@>@/
  81. @<The |main| program@>
  82.  
  83. @ The library interfaces of \PASCAL/ and \CEE/ are different, so here is
  84. the needed information.
  85. @^Differences between \PASCAL/ and \CEE/@>
  86.  
  87. @<Global \#|include|s@>=
  88. #include <stdio.h>
  89. #include <stdlib.h>
  90. #include <limits.h>
  91. #include <time.h>
  92.  
  93. @ The global variables $M$ and $N$ have already been mentioned; we had
  94. better declare them.  Other global variables will be declared later.
  95.  
  96. @<Global variables@>=
  97.    int M; /* size of the sample */
  98.    int N; /* size of the population */
  99.  
  100. @ In \CEE/ it's very easy to generate random integers in the range $i\ldots
  101. j$, so the assumed external routine for the \PASCAL/ version comes down to
  102. this.
  103. @^Differences between \PASCAL/ and \CEE/@>
  104.  
  105. @<The random number generation procedure@>=
  106. int rand_int(int i,int j)
  107.    {
  108.    return(i + rand()%(j-i+1));
  109.    }
  110.  
  111. @ @<Initialize the random number generator@>=
  112.    srand((unsigned)time(NULL) % UINT_MAX);
  113. @^Differences between \PASCAL/ and \CEE/@>
  114.  
  115. @* A plan of attack.  After the user has specified $M$ and $N$, we compute
  116. the sample by following a general procedure recommended by Bentley:
  117. @^Bentley, Jon Louis@>
  118.  
  119. @<The |main| program@>=
  120. void main(void)
  121.    {
  122.    @<Establish the values of |M| and |N|@>@;
  123.    @<Initialize the random number generator@>@;
  124.    size = 0; @+ @<Initialize set |S| to empty@>@;
  125.    while(size < M) {
  126.       T = rand_int(1,N);
  127.       @<If |T| is not in |S|, insert it and increase |size|@>@;
  128.       }
  129.    @<Print the elements of |S| in sorted order@>@;
  130.    @<Free the allocated |hash| table@>@;
  131.    }
  132.  
  133. @ The main program just sketched has introduced several more global
  134. variables.  There's a set |S| of integers, whose representation will be
  135. deferred until later; but we can declare two auxiliary integer variables
  136. now.
  137.  
  138. @<Global variables@>=
  139.    int size; /* the number of elements in set |S| */
  140.    int T; /* new candidate for membership in |S| */
  141.  
  142. @ The first order of business is to have a short dialog with the user.
  143.  
  144. @<Establish the values of |M| and |N|@>=
  145.    do @+ {
  146.       printf("population size: N = "); @+ read_terminal(N);
  147.       if(N<=0) printf("N should be positive!\n");
  148.       } @+ while(N<=0);
  149.    do @+ {
  150.       printf("sample size: M = "); @+ read_terminal(M);
  151.       if(M<0) printf("M shouldn't be negative!\n");
  152.       else if(M>N) printf("M shouldn't exceed N!\n");
  153.       } @+ while((M<0) || (M>N));
  154.  
  155. @ @<Free the allocated |hash| table@>=
  156.    if(hash) free(hash);
  157. @^Differences between \PASCAL/ and \CEE/@>
  158.  
  159. @* An ordered hash table.  The key idea to an efficient solution of this
  160. sampling problem is to maintain a set whose entries are easily sorted.  The
  161. method of ``ordered hash tables'' [Amble and Knuth, {\sl The Computer
  162. Journal}~{\bf 17} (May 1974), 135--142] is ideally suited to this task, as
  163. we shall see.
  164. @^Amble, Ole@>
  165. @^Knuth, Donald E.@>
  166.  
  167. Ordered hashing is similar to ordinary linear probing, except that the
  168. relative order of keys is taken into account.  The cited paper derives
  169. theoretical results that will not be rederived here, but we shall use the
  170. following fundamental property: {\sl The entries of an ordered hash table
  171. are independent of the order in which its keys were inserted.}  Thus, an
  172. ordered hash table is a ``canonical'' representation of its set of entries.
  173.  
  174. We shall represent |S| by an array of $2M$ integers.
  175.  
  176. @<Global variables@>=
  177.    int *hash; /* (a pointer to) the ordered hash table */
  178.    int H; /* an index into |hash| */
  179.    int H_max; /* the current hash size */
  180.    float alpha; /* the ratio of table size to |N| */
  181. @^Differences between \PASCAL/ and \CEE/@>
  182.  
  183. @ @<Initialize set |S| to empty@>=
  184.    if(hash = (int *)calloc(2*M,sizeof(int))) {
  185.       H_max = 2*M-1; @+ alpha = 2*M/N;
  186.       for(H=0; H<=H_max; H++)
  187.          hash[H] = 0;
  188.       }
  189.    else exit(1);
  190. @^Differences between \PASCAL/ and \CEE/@>
  191.  
  192. @ Now we come to the interesting part, where the algorithm tries to insert
  193. |T| into an ordered hash table.  We use the hash address $H=\lfloor
  194. 2M(T-1)/N\rfloor$ as a starting point, since this quantity is monotonic in
  195. |T| and almost uniformly distributed in the range $0\leq H<2M$.
  196.  
  197. @<If |T| is not in |S|, insert it and increase |size|@>=
  198.    H=trunc(alpha*(T-1));
  199.    while(hash[H]>T)
  200.       if(H==0)
  201.          H=H_max;
  202.       else
  203.          H--;
  204.    if(hash[H]<T) { /* |T| is not present */
  205.       size++; @+ @<Insert |T| into the ordered hash table@>@;
  206.    }
  207.  
  208. @ The heart of ordered hashing is the insertion process.  In general, the
  209. new key |T| will be inserted in place of a previous key $T_1<T$, which is
  210. then reinserted in place of $T_2<T_1$, etc., until an empty slot is
  211. discovered.
  212.  
  213. @<Insert |T| into the ordered hash table@>=
  214.    while(hash[H]>0) {
  215.       TT=hash[H]; /* we have $0<TT<T$ */
  216.       hash[H]=T; @+ T=TT;
  217.       do @+ {
  218.          if(H==0)
  219.             H=H_max;
  220.          else
  221.             H--;
  222.          } @+ while(hash[H]>=T);
  223.       }
  224.    hash[H]=T;
  225.  
  226. @ @<Global variables@>=
  227.    int TT; /* a key that's being moved */
  228.  
  229. @* Sorting in linear time.  The climax of this program is the fact that the
  230. entries in our ordered hash table can easily be read out in increasing
  231. order.
  232.  
  233. Why is this true?  Well, we know that the final state of the table is
  234. independent of the order in which the elements entered.  Furthermore it's
  235. easy to understand what the table looks like when the entries are inserted
  236. in decreasing order, because we have used a monontonic hash function.
  237. Therefore we know that the table must have an especially simple form.
  238.  
  239. Suppose the nonzero entries are $T_1<\dots<T_M$. If $k$ of these have
  240. ``wrapped around'' in the insertion process (i.e., if |H| passed from $0$
  241. to |H_max|, $k$ times), table position |hash[0]| will either be zero (in
  242. which case $k$ must also be zero) or it will contain $T_{k+1}$.  In the
  243. latter case, the entries $T_{k+1}<\dots<T_M$ and $T_1<\dots<T_k$ will
  244. appear in order from left to right.  Thus the output can be sorted with at
  245. most two passes over the table.
  246.  
  247. @d print_it printf("%10d\n",hash[H])
  248.  
  249. @<Print the elements of |S| in sorted order@>=
  250.    if(hash[0]==0) { /* there was no wrap-around */
  251.       for(H=1; H<=H_max; H++)
  252.          if(hash[H]>0)
  253.             print_it;
  254.       }
  255.    else { /* print the wrap-around entries */
  256.       for(H=1; H<=H_max; H++)
  257.          if(hash[H]>0)
  258.             if(hash[H]<hash[0])
  259.                print_it;
  260.       for(H=0; H<=H_max; H++)
  261.           if(hash[H]>=hash[0])
  262.              print_it;
  263.       }
  264.  
  265. @* Index.  The uses of single-letter variables aren't indexed by \.{CWEB},
  266. so this list is quite short.  (And an index is quite pointless anyway, for
  267. a program of this size.)  Changes for \.{CWEB} can be found under the
  268. entry ``Differences between \PASCAL/ and \CEE/.''
  269.